The output of most of the R chunks isn’t included in the HTML version of the file to keep it to a more reasonable file size. You can run the code in R to see the output.
This is an R Markdown document. Follow the link to learn more about R Markdown and the notebook format used during the workshop.
Setup
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✓ ggplot2 3.3.4 ✓ purrr 0.3.4
## ✓ tibble 3.1.2 ✓ dplyr 1.0.6
## ✓ tidyr 1.1.3 ✓ stringr 1.4.0
## ✓ readr 1.4.0 ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
This gives you info on which packages it actually loaded, because when you install tidyverse, it installs ~25 packages, but it only loads the ones listed. Tidyverse packages also tend to be verbose in warning you when there are functions with the same name in multiple packages.
Background
Tidyverse packages do a few things:
- fix some of the annoying parts of using R, such as changing default options when importing data files and preventing large data frames from printing to the console
- are focused on working with data frames (and their columns), rather than individual vectors
- usually take a data frame as the first input to a function, and return a data frame as the output of a function, so that function calls can be more easily strung together in a sequence
- share some common naming conventions for functions and arguments that have a goal of making code more readable
- tend to be verbose, opinionated, and are actively working to provide more useful error messages
Tidyverse packages are particularly useful for:
- data exploration
- reshaping data sets
- computing summary measures over groups
- cleaning up different types of data
- reading and writing data
Data
Let’s import the data we’ll be using. The data is from the Stanford Open Policing Project and includes vehicle stops by the Evanston police in 2017. We’re reading the data in from a URL directly.
We’re going to use the read_csv function from the readr package, which is part of the tidyverse. The read_csv function works like read.csv except is has some different defaults, guesses data types a bit differently, and produces a tibble instead of a normal data frame (details coming).
police <- read_csv("https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv")
##
## ── Column specification ────────────────────────────────────────────────────────
## cols(
## .default = col_character(),
## raw_row_number = col_double(),
## date = col_date(format = ""),
## time = col_time(format = ""),
## location = col_double(),
## beat = col_double(),
## subject_age = col_logical(),
## department_id = col_double(),
## citation_issued = col_logical(),
## warning_issued = col_logical(),
## contraband_found = col_logical(),
## contraband_drugs = col_logical(),
## contraband_weapons = col_logical(),
## search_conducted = col_logical(),
## search_person = col_logical(),
## search_vehicle = col_logical(),
## vehicle_year = col_double(),
## raw_DriverRace = col_double(),
## raw_ReasonForStop = col_double(),
## raw_TypeOfMovingViolation = col_double(),
## raw_ResultOfStop = col_double()
## )
## ℹ Use `spec()` for the full column specifications.
## Warning: 7 parsing failures.
## row col expected actual file
## 1015 beat no trailing characters 78Q 'https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv'
## 1040 beat a double / 'https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv'
## 1295 beat no trailing characters 71` 'https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv'
## 1891 beat a double CHICAGO 'https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv'
## 4335 beat no trailing characters 71? 'https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv'
## .... .... ...................... ....... ...............................................................................
## See problems(...) for more details.
The output message that you get tells you want data type it guessed for each column based on the format of the information. col_double() means numeric data. col_logical() means boolean TRUE/FALSE values. Note that it also automatically read and identified date and time values and converted them to date and time objects – not just string/character data.
At the bottom of the output, it also tells you about any problems it encountered: when if found a value that doesn’t match what it thinks is the data type for the column. When it guesses the data type for each column, it only uses the first 1000 rows by default.
In this case, all of these parsing failures are in the same column: “beat”. If we look at what type it read in “beat” as above, it says col_double(), which is a numeric type. And if we look at the initial values of this variable, it does look numeric:
police$beat[1:10]
For values that can’t be automatically converted to the column type, it fills in missing. Look at row 1015, one of the rows with a parsing error:
police[1015, "beat"]
Sometimes, we might be OK with having these values go to missing. But we can also manually specify column types for cases where the assumption that read_csv makes is wrong. We use the col_types argument (similar to colClasses for read.csv). Let’s make the “beat” column character data, since while the beats do have numbers, we wouldn’t want to treat the values as numeric anyway (e.g. we wouldn’t average the beat). While we’re at it, let’s also make location to be character data, since it is zip codes.
police <- read_csv("https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv",
col_types=c("beat"="c", "location"="c"))
One way to specify col_types is with a named vector, where the name is the name of the column (“beat”) and the value is the type of the data: “c” is short for “c”haracter data.
This time we don’t get any parsing errors.
EXERCISE
Remember: you need to have loaded tidyverse, so execute the cells above.
We have a dataset that includes ISO two-letter country codes. The country code for Namibia is NA, so we don’t want to read “NA” in as missing.
Look at the documentation (help) page for read_csv. You can open it by typing ?read_csv in the console. The na argument determines what values are imported as missing NA.
Change the code below so that only empty strings "" and “N/A” values are imported as missing. Look at fix_data after importing so you can check the values.
fix_data <- read_csv("https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/missing.csv")
fix_data
Tibbles
You may have noticed above that read_csv imported the data as something called a Tibble. Tibbles are the tidyverse version of a data frame. You can use them as you would a data frame (they are one), but they behave in slightly different ways.
police
The most observable difference is that tibbles will only print 10 rows and the columns that will fit in your console. When they print, they print a list of column names and the types of the columns that are shown.
To view the dataset, use View():
View(police)
When using [] notation to subset them, they will always return a tibble. In contrast, data frames sometimes return a data frame and sometimes return just a vector.
police[, 1]
as.data.frame(police)[, 1]
dplyr
dplyr is the core package of the tidyverse. It includes functions for working with tibbles (or any data frames). While you can still use base R operations on tibbles/data frames, such as using $ and [] subsetting like we did above, dplyr provides alternatives to all of the common data manipulation tasks.
Here, we’re just going to look at the basics of subsetting data to get a feel for how tidyverse functions typically work. Next session, we’ll get into variations on subsetting data and some other dplyr functions.
Before we start, let’s remember what columns are in our data:
names(police)
select
The select() function lets us choose which columns (or variables) we want to keep in our data.
The data frame is the first input, and the name of the column is the second. We do not have to put quotes around the column name.
select(police, subject_race)
If we want to select additional columns, we can just list the column names as additional inputs, each column name separated by commas:
select(police, subject_race, outcome)
As with [] indexing, columns will be returned in the order specified:
select(police, subject_sex, subject_race, date)
We could also use the column index number if we wanted to instead. We don’t need to put the values in c() like we would with [] (but we could).
select(police, 1, 4, 10)
Yes, there are other ways to specify which columns you want. We’ll cover those next session.
EXERCISE
Remember: you need to have loaded tidyverse, and the police data, so execute the cells above.
Convert this base R expression: police[,c("violation", "citation_issued", "warning_issued")] to use select() instead to do the same thing:
filter
To choose which rows should remain in our data, we use filter(). As with [], we write expressions that evaluate to TRUE or FALSE for each row. Like select(), we can use the column names without quotes.
filter(police, location == "60202")
Note that we use == to test for equality and get TRUE/FALSE output. You can also write more complicated expressions – anything that will evaluate to a vector of TRUE/FALSE values.
filter(police, is.na(beat))
Variables (columns) that are already logical (TRUE/FALSE values), can be used to filter:
filter(police, contraband_found)
EXERCISE
Use filter() to choose the rows where subject_race is “white”.
The equivalent base R expression would be police[police$subject_race == "white",].
slice
Unlike select(), we can’t use row numbers to index which rows we want with filter. This gives an error:
filter(police, 10)
If we did need to use the row index (row number) to select which rows we want, we can use the slice() function.
slice(police, 10)
slice(police, 10:15)
We don’t usually use slice() in this way when working with dplyr. This is because we ideally want to be working with well-structured data, where we can reorder the rows without losing information. If reordering the rows in the dataset would result in a loss of information (it would mess up your data), then the dataset is missing an important variable – maybe just a sequence index. You should always be able to use a variable to order the data if needed.
Pipe: Chaining Commands Together
So, we can choose rows and choose columns separately; how do we combine these operations? dplyr, and other tidyverse, commands can be strung together is a series with a %>% (say/read: pipe) operator. If you are familiar with working in a terminal/at the command line, it works like a bash pipe character |. It takes the output of the command on the left and makes that the first input to the command on the right.
This works because the functions all take a data frame as the first input, and they return a data frame as the output.
We can rewrite
select(police, date, time)
as
police %>% select(date, time)
and you’ll often see code formatted, so %>% is at the end of each line, and the following line that are still part of the same expression are indented:
police %>%
select(date, time)
The pipe comes from a package called magrittr, which has additional special operators in it that you can use. The keyboard shortcut for %>% is command-shift-M (Mac) or control-shift-M (Windows).
We can use the pipe to string together multiple commands operating on the same data frame:
police %>%
select(subject_race, subject_sex) %>%
filter(subject_race == "white")
We would read the %>% in the command above as “then” if reading the code outloud: from police, select subject_race and subject_sex, then filter where subject_race is white.
This works because the dplyr functions take a tibble/data frame as the first argument (input) and return a tibble/data frame as the output. This makes it easy to pass a data frame through multiple operations, changing it one step at a time.
Order does matter, as the commands are executed in order. So this would give us an error:
police %>%
select(subject_sex, outcome) %>%
filter(subject_race == "white")
Because subject_race is no longer in the data frame once we try to filter with it. We’d have to reverse the order:
police %>%
filter(subject_race == "white") %>%
select(subject_sex, outcome)
You can use the pipe operator to string together commands outside of the tidyverse as well, and it works with any input and output, not just data frames:
# sum(is.na(police$beat))
is.na(police$beat) %>% sum()
EXERCISE
Select the date, time, and outcome (columns) of stops that occur in beat “71” (rows). Make use of the %>% operator.
The equivalent base R expression would be: police[police$beat == "71", c("date", "time", "outcome")]
Hint: remember that a column needs to still be in the data frame if you’re going to use the column to filter.
Note that so far, we haven’t actually changed the police data frame at all. We’ve written expressions to give us output, but we haven’t saved it.
Sometimes we may still want to save the result of some expression, such as after performing a bunch of data cleaning steps. We can assign the output of piped commands as we would with any other expression.
police60201 <- police %>%
filter(location == "60201") %>%
select(date, time, beat, type, outcome)
EXERCISE
Select only vehicle_year and vehicle_make columns for observations where there were contraband_weapons
Recap
We learned what tibbles are, the dplyr equivalents of indexing and subsetting a data frame, and the pipe %>% operator.
Next time we’re going to look at some more complicated use cases for select, filter, and slice, as well as learn mutate to create or change variables in our datasets.
LS0tCnRpdGxlOiAiVGlkeXZlcnNlIGJhc2ljcyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgpgYGB7ciwgc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMgeW91IGRvbid0IG5lZWQgdG8gcnVuIHRoaXMgd2hlbiB3b3JraW5nIGluIFJTdHVkaW8Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWw9RkFMU0UpICAjIHdoZW4gbWFraW5nIHRoZSBodG1sIHZlcnNpb24gb2YgdGhpcyBmaWxlLCBkb24ndCBleGVjdXRlIHRoZSBjb2RlCmBgYAoKKlRoZSBvdXRwdXQgb2YgbW9zdCBvZiB0aGUgUiBjaHVua3MgaXNuJ3QgaW5jbHVkZWQgaW4gdGhlIEhUTUwgdmVyc2lvbiBvZiB0aGUgZmlsZSB0byBrZWVwIGl0IHRvIGEgbW9yZSByZWFzb25hYmxlIGZpbGUgc2l6ZS4gIFlvdSBjYW4gcnVuIHRoZSBjb2RlIGluIFIgdG8gc2VlIHRoZSBvdXRwdXQuKgoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cHM6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vKSBkb2N1bWVudC4gIEZvbGxvdyB0aGUgbGluayB0byBsZWFybiBtb3JlIGFib3V0IFIgTWFya2Rvd24gYW5kIHRoZSBub3RlYm9vayBmb3JtYXQgdXNlZCBkdXJpbmcgdGhlIHdvcmtzaG9wLgoKIyBTZXR1cAoKYGBge3IsIGV2YWw9VFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKVGhpcyBnaXZlcyB5b3UgaW5mbyBvbiB3aGljaCBwYWNrYWdlcyBpdCBhY3R1YWxseSBsb2FkZWQsIGJlY2F1c2Ugd2hlbiB5b3UgaW5zdGFsbCB0aWR5dmVyc2UsIGl0IGluc3RhbGxzIH4yNSBwYWNrYWdlcywgYnV0IGl0IG9ubHkgbG9hZHMgdGhlIG9uZXMgbGlzdGVkLiAgVGlkeXZlcnNlIHBhY2thZ2VzIGFsc28gdGVuZCB0byBiZSB2ZXJib3NlIGluIHdhcm5pbmcgeW91IHdoZW4gdGhlcmUgYXJlIGZ1bmN0aW9ucyB3aXRoIHRoZSBzYW1lIG5hbWUgaW4gbXVsdGlwbGUgcGFja2FnZXMuCgojIEJhY2tncm91bmQKClRpZHl2ZXJzZSBwYWNrYWdlcyBkbyBhIGZldyB0aGluZ3M6CgoqIGZpeCBzb21lIG9mIHRoZSBhbm5veWluZyBwYXJ0cyBvZiB1c2luZyBSLCBzdWNoIGFzIGNoYW5naW5nIGRlZmF1bHQgb3B0aW9ucyB3aGVuIGltcG9ydGluZyBkYXRhIGZpbGVzIGFuZCBwcmV2ZW50aW5nIGxhcmdlIGRhdGEgZnJhbWVzIGZyb20gcHJpbnRpbmcgdG8gdGhlIGNvbnNvbGUKKiBhcmUgZm9jdXNlZCBvbiB3b3JraW5nIHdpdGggZGF0YSBmcmFtZXMgKGFuZCB0aGVpciBjb2x1bW5zKSwgcmF0aGVyIHRoYW4gaW5kaXZpZHVhbCB2ZWN0b3JzCiogdXN1YWxseSB0YWtlIGEgZGF0YSBmcmFtZSBhcyB0aGUgZmlyc3QgaW5wdXQgdG8gYSBmdW5jdGlvbiwgYW5kIHJldHVybiBhIGRhdGEgZnJhbWUgYXMgdGhlIG91dHB1dCBvZiBhIGZ1bmN0aW9uLCBzbyB0aGF0IGZ1bmN0aW9uIGNhbGxzIGNhbiBiZSBtb3JlIGVhc2lseSBzdHJ1bmcgdG9nZXRoZXIgaW4gYSBzZXF1ZW5jZQoqIHNoYXJlIHNvbWUgY29tbW9uIG5hbWluZyBjb252ZW50aW9ucyBmb3IgZnVuY3Rpb25zIGFuZCBhcmd1bWVudHMgdGhhdCBoYXZlIGEgZ29hbCBvZiBtYWtpbmcgY29kZSBtb3JlIHJlYWRhYmxlCiogdGVuZCB0byBiZSB2ZXJib3NlLCBvcGluaW9uYXRlZCwgYW5kIGFyZSBhY3RpdmVseSB3b3JraW5nIHRvIHByb3ZpZGUgbW9yZSB1c2VmdWwgZXJyb3IgbWVzc2FnZXMKClRpZHl2ZXJzZSBwYWNrYWdlcyBhcmUgcGFydGljdWxhcmx5IHVzZWZ1bCBmb3I6CgoqIGRhdGEgZXhwbG9yYXRpb24KKiByZXNoYXBpbmcgZGF0YSBzZXRzCiogY29tcHV0aW5nIHN1bW1hcnkgbWVhc3VyZXMgb3ZlciBncm91cHMKKiBjbGVhbmluZyB1cCBkaWZmZXJlbnQgdHlwZXMgb2YgZGF0YQoqIHJlYWRpbmcgYW5kIHdyaXRpbmcgZGF0YQoKIyBEYXRhCgpMZXQncyBpbXBvcnQgdGhlIGRhdGEgd2UnbGwgYmUgdXNpbmcuICBUaGUgZGF0YSBpcyBmcm9tIHRoZSBbU3RhbmZvcmQgT3BlbiBQb2xpY2luZyBQcm9qZWN0XShodHRwczovL29wZW5wb2xpY2luZy5zdGFuZm9yZC5lZHUvZGF0YS8pIGFuZCBpbmNsdWRlcyB2ZWhpY2xlIHN0b3BzIGJ5IHRoZSBFdmFuc3RvbiBwb2xpY2UgaW4gMjAxNy4gIFdlJ3JlIHJlYWRpbmcgdGhlIGRhdGEgaW4gZnJvbSBhIFVSTCBkaXJlY3RseS4gIAoKV2UncmUgZ29pbmcgdG8gdXNlIHRoZSBgcmVhZF9jc3ZgIGZ1bmN0aW9uIGZyb20gdGhlIGByZWFkcmAgcGFja2FnZSwgd2hpY2ggaXMgcGFydCBvZiB0aGUgdGlkeXZlcnNlLiAgVGhlIGByZWFkX2NzdmAgZnVuY3Rpb24gd29ya3MgbGlrZSBgcmVhZC5jc3ZgIGV4Y2VwdCBpcyBoYXMgc29tZSBkaWZmZXJlbnQgZGVmYXVsdHMsIGd1ZXNzZXMgZGF0YSB0eXBlcyBhIGJpdCBkaWZmZXJlbnRseSwgYW5kIHByb2R1Y2VzIGEgdGliYmxlIGluc3RlYWQgb2YgYSBub3JtYWwgZGF0YSBmcmFtZSAoZGV0YWlscyBjb21pbmcpLiAgCgpgYGB7ciwgZXZhbD1UUlVFfQpwb2xpY2UgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9udWl0cmNzL3ItdGlkeXZlcnNlL21haW4vZGF0YS9ldl9wb2xpY2UuY3N2IikKYGBgCgpUaGUgb3V0cHV0IG1lc3NhZ2UgdGhhdCB5b3UgZ2V0IHRlbGxzIHlvdSB3YW50IGRhdGEgdHlwZSBpdCBndWVzc2VkIGZvciBlYWNoIGNvbHVtbiBiYXNlZCBvbiB0aGUgZm9ybWF0IG9mIHRoZSBpbmZvcm1hdGlvbi4gIGBjb2xfZG91YmxlKClgIG1lYW5zIG51bWVyaWMgZGF0YS4gIGBjb2xfbG9naWNhbCgpYCBtZWFucyBib29sZWFuIFRSVUUvRkFMU0UgdmFsdWVzLiAgTm90ZSB0aGF0IGl0IGFsc28gYXV0b21hdGljYWxseSByZWFkIGFuZCBpZGVudGlmaWVkIGRhdGUgYW5kIHRpbWUgdmFsdWVzIGFuZCBjb252ZXJ0ZWQgdGhlbSB0byBkYXRlIGFuZCB0aW1lIG9iamVjdHMgLS0gbm90IGp1c3Qgc3RyaW5nL2NoYXJhY3RlciBkYXRhLiAgICAKCkF0IHRoZSBib3R0b20gb2YgdGhlIG91dHB1dCwgaXQgYWxzbyB0ZWxscyB5b3UgYWJvdXQgYW55IHByb2JsZW1zIGl0IGVuY291bnRlcmVkOiB3aGVuIGlmIGZvdW5kIGEgdmFsdWUgdGhhdCBkb2Vzbid0IG1hdGNoIHdoYXQgaXQgdGhpbmtzIGlzIHRoZSBkYXRhIHR5cGUgZm9yIHRoZSBjb2x1bW4uICBXaGVuIGl0IGd1ZXNzZXMgdGhlIGRhdGEgdHlwZSBmb3IgZWFjaCBjb2x1bW4sIGl0IG9ubHkgdXNlcyB0aGUgZmlyc3QgMTAwMCByb3dzIGJ5IGRlZmF1bHQuIAoKSW4gdGhpcyBjYXNlLCBhbGwgb2YgdGhlc2UgcGFyc2luZyBmYWlsdXJlcyBhcmUgaW4gdGhlIHNhbWUgY29sdW1uOiAiYmVhdCIuICBJZiB3ZSBsb29rIGF0IHdoYXQgdHlwZSBpdCByZWFkIGluICJiZWF0IiBhcyBhYm92ZSwgaXQgc2F5cyBgY29sX2RvdWJsZSgpYCwgd2hpY2ggaXMgYSBudW1lcmljIHR5cGUuICBBbmQgaWYgd2UgbG9vayBhdCB0aGUgaW5pdGlhbCB2YWx1ZXMgb2YgdGhpcyB2YXJpYWJsZSwgaXQgZG9lcyBsb29rIG51bWVyaWM6CgpgYGB7cn0KcG9saWNlJGJlYXRbMToxMF0KYGBgCgoKRm9yIHZhbHVlcyB0aGF0IGNhbid0IGJlIGF1dG9tYXRpY2FsbHkgY29udmVydGVkIHRvIHRoZSBjb2x1bW4gdHlwZSwgaXQgZmlsbHMgaW4gbWlzc2luZy4gIExvb2sgYXQgcm93IDEwMTUsIG9uZSBvZiB0aGUgcm93cyB3aXRoIGEgcGFyc2luZyBlcnJvcjoKCmBgYHtyfQpwb2xpY2VbMTAxNSwgImJlYXQiXQpgYGAKClNvbWV0aW1lcywgd2UgbWlnaHQgYmUgT0sgd2l0aCBoYXZpbmcgdGhlc2UgdmFsdWVzIGdvIHRvIG1pc3NpbmcuICBCdXQgd2UgY2FuIGFsc28gbWFudWFsbHkgc3BlY2lmeSBjb2x1bW4gdHlwZXMgZm9yIGNhc2VzIHdoZXJlIHRoZSBhc3N1bXB0aW9uIHRoYXQgYHJlYWRfY3N2YCBtYWtlcyBpcyB3cm9uZy4gIFdlIHVzZSB0aGUgYGNvbF90eXBlc2AgYXJndW1lbnQgKHNpbWlsYXIgdG8gY29sQ2xhc3NlcyBmb3IgYHJlYWQuY3N2YCkuICBMZXQncyBtYWtlIHRoZSAiYmVhdCIgY29sdW1uIGNoYXJhY3RlciBkYXRhLCBzaW5jZSB3aGlsZSB0aGUgYmVhdHMgZG8gaGF2ZSBudW1iZXJzLCB3ZSB3b3VsZG4ndCB3YW50IHRvIHRyZWF0IHRoZSB2YWx1ZXMgYXMgbnVtZXJpYyBhbnl3YXkgKGUuZy4gd2Ugd291bGRuJ3QgYXZlcmFnZSB0aGUgYmVhdCkuIFdoaWxlIHdlJ3JlIGF0IGl0LCBsZXQncyBhbHNvIG1ha2UgbG9jYXRpb24gdG8gYmUgY2hhcmFjdGVyIGRhdGEsIHNpbmNlIGl0IGlzIHppcCBjb2Rlcy4KCgpgYGB7ciwgZXZhbD1UUlVFfQpwb2xpY2UgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9udWl0cmNzL3ItdGlkeXZlcnNlL21haW4vZGF0YS9ldl9wb2xpY2UuY3N2IiwKICAgICAgICAgICAgICAgICAgIGNvbF90eXBlcz1jKCJiZWF0Ij0iYyIsICJsb2NhdGlvbiI9ImMiKSkKYGBgCgpPbmUgd2F5IHRvIHNwZWNpZnkgYGNvbF90eXBlc2AgaXMgd2l0aCBhIG5hbWVkIHZlY3Rvciwgd2hlcmUgdGhlIG5hbWUgaXMgdGhlIG5hbWUgb2YgdGhlIGNvbHVtbiAoImJlYXQiKSBhbmQgdGhlIHZhbHVlIGlzIHRoZSB0eXBlIG9mIHRoZSBkYXRhOiAiYyIgaXMgc2hvcnQgZm9yICJjImhhcmFjdGVyIGRhdGEuICAKClRoaXMgdGltZSB3ZSBkb24ndCBnZXQgYW55IHBhcnNpbmcgZXJyb3JzLgoKIyMjIEVYRVJDSVNFCgpSZW1lbWJlcjogeW91IG5lZWQgdG8gaGF2ZSBsb2FkZWQgdGlkeXZlcnNlLCBzbyBleGVjdXRlIHRoZSBjZWxscyBhYm92ZS4KCldlIGhhdmUgYSBkYXRhc2V0IHRoYXQgaW5jbHVkZXMgW0lTTyB0d28tbGV0dGVyIGNvdW50cnkgY29kZXNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0lTT18zMTY2LTFfYWxwaGEtMikuICBUaGUgY291bnRyeSBjb2RlIGZvciBOYW1pYmlhIGlzIE5BLCBzbyB3ZSBkb24ndCB3YW50IHRvIHJlYWQgIk5BIiBpbiBhcyBtaXNzaW5nLgoKTG9vayBhdCB0aGUgZG9jdW1lbnRhdGlvbiAoaGVscCkgcGFnZSBmb3IgYHJlYWRfY3N2YC4gIFlvdSBjYW4gb3BlbiBpdCBieSB0eXBpbmcgYD9yZWFkX2NzdmAgaW4gdGhlIGNvbnNvbGUuICBUaGUgYG5hYCBhcmd1bWVudCBkZXRlcm1pbmVzIHdoYXQgdmFsdWVzIGFyZSBpbXBvcnRlZCBhcyBtaXNzaW5nIGBOQWAuICAKCkNoYW5nZSB0aGUgY29kZSBiZWxvdyBzbyB0aGF0ICoqb25seSoqIGVtcHR5IHN0cmluZ3MgIiIgYW5kICJOL0EiIHZhbHVlcyBhcmUgaW1wb3J0ZWQgYXMgbWlzc2luZy4gIExvb2sgYXQgYGZpeF9kYXRhYCBhZnRlciBpbXBvcnRpbmcgc28geW91IGNhbiBjaGVjayB0aGUgdmFsdWVzLgoKYGBge3J9CmZpeF9kYXRhIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbnVpdHJjcy9yLXRpZHl2ZXJzZS9tYWluL2RhdGEvbWlzc2luZy5jc3YiKQpmaXhfZGF0YQpgYGAKCgojIFRpYmJsZXMKCllvdSBtYXkgaGF2ZSBub3RpY2VkIGFib3ZlIHRoYXQgYHJlYWRfY3N2YCBpbXBvcnRlZCB0aGUgZGF0YSBhcyBzb21ldGhpbmcgY2FsbGVkIGEgVGliYmxlLiAgVGliYmxlcyBhcmUgdGhlIHRpZHl2ZXJzZSB2ZXJzaW9uIG9mIGEgZGF0YSBmcmFtZS4gIFlvdSBjYW4gdXNlIHRoZW0gYXMgeW91IHdvdWxkIGEgZGF0YSBmcmFtZSAodGhleSBhcmUgb25lKSwgYnV0IHRoZXkgYmVoYXZlIGluIHNsaWdodGx5IGRpZmZlcmVudCB3YXlzLgoKYGBge3IsIGV2YWw9VFJVRX0KcG9saWNlCmBgYAoKVGhlIG1vc3Qgb2JzZXJ2YWJsZSBkaWZmZXJlbmNlIGlzIHRoYXQgdGliYmxlcyB3aWxsIG9ubHkgcHJpbnQgMTAgcm93cyBhbmQgdGhlIGNvbHVtbnMgdGhhdCB3aWxsIGZpdCBpbiB5b3VyIGNvbnNvbGUuICBXaGVuIHRoZXkgcHJpbnQsIHRoZXkgcHJpbnQgYSBsaXN0IG9mIGNvbHVtbiBuYW1lcyBhbmQgdGhlIHR5cGVzIG9mIHRoZSBjb2x1bW5zIHRoYXQgYXJlIHNob3duLiAgCgpUbyB2aWV3IHRoZSBkYXRhc2V0LCB1c2UgYFZpZXcoKWA6CgpgYGB7cn0KVmlldyhwb2xpY2UpCmBgYAoKV2hlbiB1c2luZyBbXSBub3RhdGlvbiB0byBzdWJzZXQgdGhlbSwgdGhleSB3aWxsIGFsd2F5cyByZXR1cm4gYSB0aWJibGUuICBJbiBjb250cmFzdCwgZGF0YSBmcmFtZXMgc29tZXRpbWVzIHJldHVybiBhIGRhdGEgZnJhbWUgYW5kIHNvbWV0aW1lcyByZXR1cm4ganVzdCBhIHZlY3Rvci4KCmBgYHtyfQpwb2xpY2VbLCAxXQphcy5kYXRhLmZyYW1lKHBvbGljZSlbLCAxXQpgYGAKCiMgZHBseXIKCmRwbHlyIGlzIHRoZSBjb3JlIHBhY2thZ2Ugb2YgdGhlIHRpZHl2ZXJzZS4gIEl0IGluY2x1ZGVzIGZ1bmN0aW9ucyBmb3Igd29ya2luZyB3aXRoIHRpYmJsZXMgKG9yIGFueSBkYXRhIGZyYW1lcykuICBXaGlsZSB5b3UgY2FuIHN0aWxsIHVzZSBiYXNlIFIgb3BlcmF0aW9ucyBvbiB0aWJibGVzL2RhdGEgZnJhbWVzLCBzdWNoIGFzIHVzaW5nIGAkYCBhbmQgYFtdYCBzdWJzZXR0aW5nIGxpa2Ugd2UgZGlkIGFib3ZlLCBkcGx5ciBwcm92aWRlcyBhbHRlcm5hdGl2ZXMgdG8gYWxsIG9mIHRoZSBjb21tb24gZGF0YSBtYW5pcHVsYXRpb24gdGFza3MuCgpIZXJlLCB3ZSdyZSBqdXN0IGdvaW5nIHRvIGxvb2sgYXQgdGhlIGJhc2ljcyBvZiBzdWJzZXR0aW5nIGRhdGEgdG8gZ2V0IGEgZmVlbCBmb3IgaG93IHRpZHl2ZXJzZSBmdW5jdGlvbnMgdHlwaWNhbGx5IHdvcmsuICAgTmV4dCBzZXNzaW9uLCB3ZSdsbCBnZXQgaW50byB2YXJpYXRpb25zIG9uIHN1YnNldHRpbmcgZGF0YSBhbmQgc29tZSBvdGhlciBkcGx5ciBmdW5jdGlvbnMuCgpCZWZvcmUgd2Ugc3RhcnQsIGxldCdzIHJlbWVtYmVyIHdoYXQgY29sdW1ucyBhcmUgaW4gb3VyIGRhdGE6CgpgYGB7cn0KbmFtZXMocG9saWNlKQpgYGAKCgojIyBzZWxlY3QKClRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uIGxldHMgdXMgY2hvb3NlIHdoaWNoIGNvbHVtbnMgKG9yIHZhcmlhYmxlcykgd2Ugd2FudCB0byBrZWVwIGluIG91ciBkYXRhLgoKVGhlIGRhdGEgZnJhbWUgaXMgdGhlIGZpcnN0IGlucHV0LCBhbmQgdGhlIG5hbWUgb2YgdGhlIGNvbHVtbiBpcyB0aGUgc2Vjb25kLiAgV2UgZG8gbm90IGhhdmUgdG8gcHV0IHF1b3RlcyBhcm91bmQgdGhlIGNvbHVtbiBuYW1lLiAgCgpgYGB7cn0Kc2VsZWN0KHBvbGljZSwgc3ViamVjdF9yYWNlKQpgYGAKCklmIHdlIHdhbnQgdG8gc2VsZWN0IGFkZGl0aW9uYWwgY29sdW1ucywgd2UgY2FuIGp1c3QgbGlzdCB0aGUgY29sdW1uIG5hbWVzIGFzIGFkZGl0aW9uYWwgaW5wdXRzLCBlYWNoIGNvbHVtbiBuYW1lIHNlcGFyYXRlZCBieSBjb21tYXM6CgpgYGB7cn0Kc2VsZWN0KHBvbGljZSwgc3ViamVjdF9yYWNlLCBvdXRjb21lKQpgYGAKCkFzIHdpdGggYFtdYCBpbmRleGluZywgY29sdW1ucyB3aWxsIGJlIHJldHVybmVkIGluIHRoZSBvcmRlciBzcGVjaWZpZWQ6CgpgYGB7cn0Kc2VsZWN0KHBvbGljZSwgc3ViamVjdF9zZXgsIHN1YmplY3RfcmFjZSwgZGF0ZSkKYGBgCgoKV2UgY291bGQgYWxzbyB1c2UgdGhlIGNvbHVtbiBpbmRleCBudW1iZXIgaWYgd2Ugd2FudGVkIHRvIGluc3RlYWQuICBXZSBkb24ndCBuZWVkIHRvIHB1dCB0aGUgdmFsdWVzIGluIGBjKClgIGxpa2Ugd2Ugd291bGQgd2l0aCBgW11gIChidXQgd2UgY291bGQpLgoKYGBge3J9CnNlbGVjdChwb2xpY2UsIDEsIDQsIDEwKQpgYGAKClllcywgdGhlcmUgYXJlIG90aGVyIHdheXMgdG8gc3BlY2lmeSB3aGljaCBjb2x1bW5zIHlvdSB3YW50LiAgV2UnbGwgY292ZXIgdGhvc2UgbmV4dCBzZXNzaW9uLiAgCgojIyMgRVhFUkNJU0UKClJlbWVtYmVyOiB5b3UgbmVlZCB0byBoYXZlIGxvYWRlZCB0aWR5dmVyc2UsIGFuZCB0aGUgcG9saWNlIGRhdGEsIHNvIGV4ZWN1dGUgdGhlIGNlbGxzIGFib3ZlLgoKQ29udmVydCB0aGlzIGJhc2UgUiBleHByZXNzaW9uOiBgcG9saWNlWyxjKCJ2aW9sYXRpb24iLCAiY2l0YXRpb25faXNzdWVkIiwgIndhcm5pbmdfaXNzdWVkIildYCB0byB1c2UgYHNlbGVjdCgpYCBpbnN0ZWFkIHRvIGRvIHRoZSBzYW1lIHRoaW5nOiAKCmBgYHtyfQoKYGBgCgoKCiMjIGZpbHRlcgoKVG8gY2hvb3NlIHdoaWNoIHJvd3Mgc2hvdWxkIHJlbWFpbiBpbiBvdXIgZGF0YSwgd2UgdXNlIGBmaWx0ZXIoKWAuICBBcyB3aXRoIGBbXWAsIHdlIHdyaXRlIGV4cHJlc3Npb25zIHRoYXQgZXZhbHVhdGUgdG8gVFJVRSBvciBGQUxTRSBmb3IgZWFjaCByb3cuICBMaWtlIGBzZWxlY3QoKWAsIHdlIGNhbiB1c2UgdGhlIGNvbHVtbiBuYW1lcyB3aXRob3V0IHF1b3Rlcy4KCgpgYGB7cn0KZmlsdGVyKHBvbGljZSwgbG9jYXRpb24gPT0gIjYwMjAyIikKYGBgCgpOb3RlIHRoYXQgd2UgdXNlIGA9PWAgdG8gdGVzdCBmb3IgZXF1YWxpdHkgYW5kIGdldCBUUlVFL0ZBTFNFIG91dHB1dC4gIFlvdSBjYW4gYWxzbyB3cml0ZSBtb3JlIGNvbXBsaWNhdGVkIGV4cHJlc3Npb25zIC0tIGFueXRoaW5nIHRoYXQgd2lsbCBldmFsdWF0ZSB0byBhIHZlY3RvciBvZiBUUlVFL0ZBTFNFIHZhbHVlcy4KCmBgYHtyfQpmaWx0ZXIocG9saWNlLCBpcy5uYShiZWF0KSkKYGBgCgpWYXJpYWJsZXMgKGNvbHVtbnMpIHRoYXQgYXJlIGFscmVhZHkgbG9naWNhbCAoVFJVRS9GQUxTRSB2YWx1ZXMpLCBjYW4gYmUgdXNlZCB0byBmaWx0ZXI6CgpgYGB7cn0KZmlsdGVyKHBvbGljZSwgY29udHJhYmFuZF9mb3VuZCkKYGBgCgoKIyMjIEVYRVJDSVNFCgpVc2UgYGZpbHRlcigpYCB0byBjaG9vc2UgdGhlIHJvd3Mgd2hlcmUgc3ViamVjdF9yYWNlIGlzICJ3aGl0ZSIuICAKClRoZSBlcXVpdmFsZW50IGJhc2UgUiBleHByZXNzaW9uIHdvdWxkIGJlIGBwb2xpY2VbcG9saWNlJHN1YmplY3RfcmFjZSA9PSAid2hpdGUiLF1gLiAgCgpgYGB7cn0KCmBgYAoKCiMjIHNsaWNlCgpVbmxpa2UgYHNlbGVjdCgpYCwgd2UgY2FuJ3QgdXNlIHJvdyBudW1iZXJzIHRvIGluZGV4IHdoaWNoIHJvd3Mgd2Ugd2FudCB3aXRoIGZpbHRlci4gIFRoaXMgZ2l2ZXMgYW4gZXJyb3I6CgpgYGB7cn0KZmlsdGVyKHBvbGljZSwgMTApCmBgYAoKSWYgd2UgZGlkIG5lZWQgdG8gdXNlIHRoZSByb3cgaW5kZXggKHJvdyBudW1iZXIpIHRvIHNlbGVjdCB3aGljaCByb3dzIHdlIHdhbnQsIHdlIGNhbiB1c2UgdGhlIGBzbGljZSgpYCBmdW5jdGlvbi4gIAoKYGBge3J9CnNsaWNlKHBvbGljZSwgMTApCmBgYAoKYGBge3J9CnNsaWNlKHBvbGljZSwgMTA6MTUpCmBgYAoKV2UgZG9uJ3QgdXN1YWxseSB1c2UgYHNsaWNlKClgIGluIHRoaXMgd2F5IHdoZW4gd29ya2luZyB3aXRoIGRwbHlyLiAgVGhpcyBpcyBiZWNhdXNlIHdlIGlkZWFsbHkgd2FudCB0byBiZSB3b3JraW5nIHdpdGggd2VsbC1zdHJ1Y3R1cmVkIGRhdGEsIHdoZXJlIHdlIGNhbiByZW9yZGVyIHRoZSByb3dzIHdpdGhvdXQgbG9zaW5nIGluZm9ybWF0aW9uLiAgSWYgcmVvcmRlcmluZyB0aGUgcm93cyBpbiB0aGUgZGF0YXNldCB3b3VsZCByZXN1bHQgaW4gYSBsb3NzIG9mIGluZm9ybWF0aW9uIChpdCB3b3VsZCBtZXNzIHVwIHlvdXIgZGF0YSksIHRoZW4gdGhlIGRhdGFzZXQgaXMgbWlzc2luZyBhbiBpbXBvcnRhbnQgdmFyaWFibGUgLS0gbWF5YmUganVzdCBhIHNlcXVlbmNlIGluZGV4LiAgWW91IHNob3VsZCBhbHdheXMgYmUgYWJsZSB0byB1c2UgYSB2YXJpYWJsZSB0byBvcmRlciB0aGUgZGF0YSBpZiBuZWVkZWQuCgojIyBQaXBlOiBDaGFpbmluZyBDb21tYW5kcyBUb2dldGhlcgoKU28sIHdlIGNhbiBjaG9vc2Ugcm93cyBhbmQgY2hvb3NlIGNvbHVtbnMgc2VwYXJhdGVseTsgaG93IGRvIHdlIGNvbWJpbmUgdGhlc2Ugb3BlcmF0aW9ucz8gIGBkcGx5cmAsIGFuZCBvdGhlciB0aWR5dmVyc2UsIGNvbW1hbmRzIGNhbiBiZSBzdHJ1bmcgdG9nZXRoZXIgaXMgYSBzZXJpZXMgd2l0aCBhIGAlPiVgIChzYXkvcmVhZDogcGlwZSkgb3BlcmF0b3IuICBJZiB5b3UgYXJlIGZhbWlsaWFyIHdpdGggd29ya2luZyBpbiBhIHRlcm1pbmFsL2F0IHRoZSBjb21tYW5kIGxpbmUsIGl0IHdvcmtzIGxpa2UgYSBiYXNoIHBpcGUgY2hhcmFjdGVyIGB8YC4gIEl0IHRha2VzIHRoZSBvdXRwdXQgb2YgdGhlIGNvbW1hbmQgb24gdGhlIGxlZnQgYW5kIG1ha2VzIHRoYXQgdGhlIGZpcnN0IGlucHV0IHRvIHRoZSBjb21tYW5kIG9uIHRoZSByaWdodC4gCgpUaGlzIHdvcmtzIGJlY2F1c2UgdGhlIGZ1bmN0aW9ucyBhbGwgdGFrZSBhIGRhdGEgZnJhbWUgYXMgdGhlIGZpcnN0IGlucHV0LCBhbmQgdGhleSByZXR1cm4gYSBkYXRhIGZyYW1lIGFzIHRoZSBvdXRwdXQuICAKCldlIGNhbiByZXdyaXRlIAoKYGBge3J9CnNlbGVjdChwb2xpY2UsIGRhdGUsIHRpbWUpCmBgYAoKYXMKCmBgYHtyfQpwb2xpY2UgJT4lIHNlbGVjdChkYXRlLCB0aW1lKQpgYGAKCmFuZCB5b3UnbGwgb2Z0ZW4gc2VlIGNvZGUgZm9ybWF0dGVkLCBzbyBgJT4lYCBpcyBhdCB0aGUgZW5kIG9mIGVhY2ggbGluZSwgYW5kIHRoZSBmb2xsb3dpbmcgbGluZSB0aGF0IGFyZSBzdGlsbCBwYXJ0IG9mIHRoZSBzYW1lIGV4cHJlc3Npb24gYXJlIGluZGVudGVkOgoKYGBge3J9CnBvbGljZSAlPiUKICBzZWxlY3QoZGF0ZSwgdGltZSkKYGBgCgpUaGUgcGlwZSBjb21lcyBmcm9tIGEgcGFja2FnZSBjYWxsZWQgYG1hZ3JpdHRyYCwgd2hpY2ggaGFzIGFkZGl0aW9uYWwgc3BlY2lhbCBvcGVyYXRvcnMgaW4gaXQgdGhhdCB5b3UgY2FuIHVzZS4gIFRoZSBrZXlib2FyZCBzaG9ydGN1dCBmb3IgYCU+JWAgaXMgY29tbWFuZC1zaGlmdC1NIChNYWMpIG9yIGNvbnRyb2wtc2hpZnQtTSAoV2luZG93cykuCgpXZSBjYW4gdXNlIHRoZSBwaXBlIHRvIHN0cmluZyB0b2dldGhlciBtdWx0aXBsZSBjb21tYW5kcyBvcGVyYXRpbmcgb24gdGhlIHNhbWUgZGF0YSBmcmFtZToKCmBgYHtyfQpwb2xpY2UgJT4lCiAgc2VsZWN0KHN1YmplY3RfcmFjZSwgc3ViamVjdF9zZXgpICU+JQogIGZpbHRlcihzdWJqZWN0X3JhY2UgPT0gIndoaXRlIikKYGBgCgpXZSB3b3VsZCByZWFkIHRoZSBgJT4lYCBpbiB0aGUgY29tbWFuZCBhYm92ZSBhcyAidGhlbiIgaWYgcmVhZGluZyB0aGUgY29kZSBvdXRsb3VkOiBmcm9tIHBvbGljZSwgc2VsZWN0IHN1YmplY3RfcmFjZSBhbmQgc3ViamVjdF9zZXgsIHRoZW4gZmlsdGVyIHdoZXJlIHN1YmplY3RfcmFjZSBpcyB3aGl0ZS4KClRoaXMgd29ya3MgYmVjYXVzZSB0aGUgZHBseXIgZnVuY3Rpb25zIHRha2UgYSB0aWJibGUvZGF0YSBmcmFtZSBhcyB0aGUgZmlyc3QgYXJndW1lbnQgKGlucHV0KSBhbmQgcmV0dXJuIGEgdGliYmxlL2RhdGEgZnJhbWUgYXMgdGhlIG91dHB1dC4gIFRoaXMgbWFrZXMgaXQgZWFzeSB0byBwYXNzIGEgZGF0YSBmcmFtZSB0aHJvdWdoIG11bHRpcGxlIG9wZXJhdGlvbnMsIGNoYW5naW5nIGl0IG9uZSBzdGVwIGF0IGEgdGltZS4gIAoKT3JkZXIgZG9lcyBtYXR0ZXIsIGFzIHRoZSBjb21tYW5kcyBhcmUgZXhlY3V0ZWQgaW4gb3JkZXIuICBTbyB0aGlzIHdvdWxkIGdpdmUgdXMgYW4gZXJyb3I6CgpgYGB7cn0KcG9saWNlICU+JQogIHNlbGVjdChzdWJqZWN0X3NleCwgb3V0Y29tZSkgJT4lCiAgZmlsdGVyKHN1YmplY3RfcmFjZSA9PSAid2hpdGUiKQpgYGAKCkJlY2F1c2UgYHN1YmplY3RfcmFjZWAgaXMgbm8gbG9uZ2VyIGluIHRoZSBkYXRhIGZyYW1lIG9uY2Ugd2UgdHJ5IHRvIGZpbHRlciB3aXRoIGl0LiAgV2UnZCBoYXZlIHRvIHJldmVyc2UgdGhlIG9yZGVyOgoKYGBge3J9CnBvbGljZSAlPiUKICBmaWx0ZXIoc3ViamVjdF9yYWNlID09ICJ3aGl0ZSIpICU+JQogIHNlbGVjdChzdWJqZWN0X3NleCwgb3V0Y29tZSkKYGBgCgpZb3UgY2FuIHVzZSB0aGUgcGlwZSBvcGVyYXRvciB0byBzdHJpbmcgdG9nZXRoZXIgY29tbWFuZHMgb3V0c2lkZSBvZiB0aGUgdGlkeXZlcnNlIGFzIHdlbGwsIGFuZCBpdCB3b3JrcyB3aXRoIGFueSBpbnB1dCBhbmQgb3V0cHV0LCBub3QganVzdCBkYXRhIGZyYW1lczoKCmBgYHtyfQojIHN1bShpcy5uYShwb2xpY2UkYmVhdCkpCmlzLm5hKHBvbGljZSRiZWF0KSAlPiUgc3VtKCkKYGBgCgoKIyMjIEVYRVJDSVNFCgpTZWxlY3QgdGhlIGRhdGUsIHRpbWUsIGFuZCBvdXRjb21lIChjb2x1bW5zKSBvZiBzdG9wcyB0aGF0IG9jY3VyIGluIGJlYXQgIjcxIiAocm93cykuICBNYWtlIHVzZSBvZiB0aGUgYCU+JWAgb3BlcmF0b3IuICAKClRoZSBlcXVpdmFsZW50IGJhc2UgUiBleHByZXNzaW9uIHdvdWxkIGJlOiBgcG9saWNlW3BvbGljZSRiZWF0ID09ICI3MSIsIGMoImRhdGUiLCAidGltZSIsICJvdXRjb21lIildYAoKSGludDogcmVtZW1iZXIgdGhhdCBhIGNvbHVtbiBuZWVkcyB0byBzdGlsbCBiZSBpbiB0aGUgZGF0YSBmcmFtZSBpZiB5b3UncmUgZ29pbmcgdG8gdXNlIHRoZSBjb2x1bW4gdG8gZmlsdGVyLgoKYGBge3J9CgpgYGAKCgpOb3RlIHRoYXQgc28gZmFyLCB3ZSBoYXZlbid0IGFjdHVhbGx5IGNoYW5nZWQgdGhlIGBwb2xpY2VgIGRhdGEgZnJhbWUgYXQgYWxsLiAgV2UndmUgd3JpdHRlbiBleHByZXNzaW9ucyB0byBnaXZlIHVzIG91dHB1dCwgYnV0IHdlIGhhdmVuJ3Qgc2F2ZWQgaXQuICAKClNvbWV0aW1lcyB3ZSBtYXkgc3RpbGwgd2FudCB0byBzYXZlIHRoZSByZXN1bHQgb2Ygc29tZSBleHByZXNzaW9uLCBzdWNoIGFzIGFmdGVyIHBlcmZvcm1pbmcgYSBidW5jaCBvZiBkYXRhIGNsZWFuaW5nIHN0ZXBzLiBXZSBjYW4gYXNzaWduIHRoZSBvdXRwdXQgb2YgcGlwZWQgY29tbWFuZHMgYXMgd2Ugd291bGQgd2l0aCBhbnkgb3RoZXIgZXhwcmVzc2lvbi4KCmBgYHtyfQpwb2xpY2U2MDIwMSA8LSBwb2xpY2UgJT4lCiAgZmlsdGVyKGxvY2F0aW9uID09ICI2MDIwMSIpICU+JQogIHNlbGVjdChkYXRlLCB0aW1lLCBiZWF0LCB0eXBlLCBvdXRjb21lKSAKYGBgCgoKCiMjIyBFWEVSQ0lTRQoKU2VsZWN0IG9ubHkgdmVoaWNsZV95ZWFyIGFuZCB2ZWhpY2xlX21ha2UgY29sdW1ucyBmb3Igb2JzZXJ2YXRpb25zIHdoZXJlIHRoZXJlIHdlcmUgY29udHJhYmFuZF93ZWFwb25zCgpgYGB7cn0KCmBgYAoKCiMgUmVjYXAKCldlIGxlYXJuZWQgd2hhdCB0aWJibGVzIGFyZSwgdGhlIGRwbHlyIGVxdWl2YWxlbnRzIG9mIGluZGV4aW5nIGFuZCBzdWJzZXR0aW5nIGEgZGF0YSBmcmFtZSwgYW5kIHRoZSBwaXBlIGAlPiVgIG9wZXJhdG9yLgoKTmV4dCB0aW1lIHdlJ3JlIGdvaW5nIHRvIGxvb2sgYXQgc29tZSBtb3JlIGNvbXBsaWNhdGVkIHVzZSBjYXNlcyBmb3IgYHNlbGVjdGAsIGBmaWx0ZXJgLCBhbmQgYHNsaWNlYCwgYXMgd2VsbCBhcyBsZWFybiBgbXV0YXRlYCB0byBjcmVhdGUgb3IgY2hhbmdlIHZhcmlhYmxlcyBpbiBvdXIgZGF0YXNldHMuICAKCgo=